﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Mvvm.Common.Commands
{
    
    public class WeakFunc<TResult>
    {

        private Func<TResult> _staticFunc;

    
        protected MethodInfo Method
        {
            get;
            set;
        }

        /// <summary>
        /// Get a value indicating whether the WeakFunc is static or not.
        /// </summary>
        public bool IsStatic
        {
            get
            {

                return _staticFunc != null;

            }
        }

        /// <summary>
        /// Gets the name of the method that this WeakFunc represents.
        /// </summary>
        public virtual string MethodName
        {
            get
            {
                if (_staticFunc != null)
                {
#if NETSTANDARD2_0
                    return _staticFunc.GetMethodInfo().Name;
#else
                    return _staticFunc.Method.Name;
#endif
                }


                return Method.Name;

            }
        }

      
        protected WeakReference FuncReference
        {
            get;
            set;
        }

    
        protected object LiveReference
        {
            get;
            set;
        }

      
        protected WeakReference Reference
        {
            get;
            set;
        }

      
        protected WeakFunc()
        {
        }

      
        public WeakFunc(Func<TResult> func, bool keepTargetAlive = false)
            : this(func == null ? null : func.Target, func, keepTargetAlive)
        {
        }

        
        public WeakFunc(object target, Func<TResult> func, bool keepTargetAlive = false)
        {
#if NETSTANDARD2_0
            if (func.GetMethodInfo().IsStatic)
#else
            if (func.Method.IsStatic)
#endif
            {
                _staticFunc = func;

                if (target != null)
                {
                    // Keep a reference to the target to control the
                    // WeakAction's lifetime.
                    Reference = new WeakReference(target);
                }

                return;
            }


#if NETSTANDARD2_0
            Method = func.GetMethodInfo();
#else
            Method = func.Method;
#endif
            FuncReference = new WeakReference(func.Target);


            LiveReference = keepTargetAlive ? func.Target : null;
            Reference = new WeakReference(target);

        }

     
        public virtual bool IsAlive
        {
            get
            {
                if (_staticFunc == null
                    && Reference == null
                    && LiveReference == null)
                {
                    return false;
                }

                if (_staticFunc != null)
                {
                    if (Reference != null)
                    {
                        return Reference.IsAlive;
                    }

                    return true;
                }

                // Non static action

                if (LiveReference != null)
                {
                    return true;
                }

                if (Reference != null)
                {
                    return Reference.IsAlive;
                }

                return false;
            }
        }

       
       

      
        protected object FuncTarget
        {
            get
            {
                if (LiveReference != null)
                {
                    return LiveReference;
                }

                if (FuncReference == null)
                {
                    return null;
                }

                return FuncReference.Target;
            }
        }

      
        public TResult Execute()
        {
            if (_staticFunc != null)
            {
                return _staticFunc();
            }

            var funcTarget = FuncTarget;

            if (IsAlive)
            {
                if (Method != null
                    && (LiveReference != null
                        || FuncReference != null)
                    && funcTarget != null)
                {
                    return (TResult)Method.Invoke(funcTarget, null);
                }


            }

            return default(TResult);
        }

      
    }
}